package com.liang.subject.infra.es.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.liang.subject.common.entity.PageDTO;
import com.liang.subject.common.enums.SubjectInfoTypeEnum;
import com.liang.subject.infra.es.config.EsIndexInfo;
import com.liang.subject.infra.es.config.EsRestClient;
import com.liang.subject.infra.es.config.EsSearchRequest;
import com.liang.subject.infra.es.config.EsSourceData;
import com.liang.subject.infra.es.entity.EsSubjectFields;
import com.liang.subject.infra.es.entity.SubjectInfoEs;
import com.liang.subject.infra.es.service.SubjectEsService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.MapUtils;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;

import static com.liang.subject.infra.es.entity.EsSubjectFields.SUBJECT_NAME;

@Slf4j
@Service
public class SubjectEsServiceImpl implements SubjectEsService {
    @Override
    public boolean insert(SubjectInfoEs subjectInfoEs) {
        EsSourceData esSourceData = new EsSourceData();
        Map<String, Object> data = convertSourceData(subjectInfoEs);
        esSourceData.setData(data);
        esSourceData.setDocId(subjectInfoEs.getDocId().toString());
        EsIndexInfo esIndexInfo = getEsIndexInfo(subjectInfoEs);
        return EsRestClient.insertDoc(esIndexInfo, esSourceData);
    }

    private static EsIndexInfo getEsIndexInfo(SubjectInfoEs subjectInfoEs) {
        EsIndexInfo esIndexInfo = new EsIndexInfo();
        esIndexInfo.setClusterName("19c2a50168bf");
        esIndexInfo.setIndexName("subject_index");
        return esIndexInfo;
    }

    private Map<String, Object> convertSourceData(SubjectInfoEs subjectInfoEs) {
        Map<String, Object> data = new HashMap<>();
        data.put(EsSubjectFields.SUBJECT_ID, subjectInfoEs.getSubjectId());
        data.put(EsSubjectFields.DOC_ID, subjectInfoEs.getDocId());
        data.put(SUBJECT_NAME, subjectInfoEs.getSubjectName());
        data.put(EsSubjectFields.SUBJECT_ANSWER, subjectInfoEs.getSubjectAnswer());
        data.put(EsSubjectFields.SUBJECT_TYPE, subjectInfoEs.getSubjectType());
        data.put(EsSubjectFields.CREATE_USER, subjectInfoEs.getCreateUser());
        data.put(EsSubjectFields.CREATE_TIME, subjectInfoEs.getCreateTime());
        return data;
    }

    @Override
    public PageDTO<SubjectInfoEs> querySubjectList(SubjectInfoEs req) {
        EsSearchRequest esSearchRequest = getEsSearchRequest(req);

        SearchResponse searchResponse = EsRestClient.searchWithTermQuery(getEsIndexInfo(req), esSearchRequest);

        List<SubjectInfoEs> subjectInfoEsList = new LinkedList<>();
        SearchHits searchHits = searchResponse.getHits();

        Page<SubjectInfoEs> page = new Page<>(req.getPageNum(), req.getPageSize());
        PageDTO<SubjectInfoEs> pageDTO = new PageDTO<>();
        if (searchHits == null || searchHits.getHits() == null) {
            pageDTO = PageDTO.of(page, SubjectInfoEs.class);
            pageDTO.setList(subjectInfoEsList);
            pageDTO.setTotal(0L);
            return pageDTO;
        }
        SearchHit[] hits = searchHits.getHits();
        for (int i = 0; i < hits.length; i++) {
            SearchHit hit = hits[i];
            SubjectInfoEs subjectInfoEs = convertResult(hit);
            if (Objects.nonNull(subjectInfoEs)) {
                subjectInfoEsList.add(subjectInfoEs);
            }
        }
        pageDTO = PageDTO.of(page, SubjectInfoEs.class);
        pageDTO.setList(subjectInfoEsList);
        pageDTO.setTotal(searchHits.getTotalHits().value);
        return pageDTO;
    }

    private EsSearchRequest getEsSearchRequest(SubjectInfoEs req) {
        BoolQueryBuilder bq = QueryBuilders.boolQuery()
                .must(QueryBuilders.matchQuery("subject_type", SubjectInfoTypeEnum.BRIEF.getCode()))
                .should(QueryBuilders.matchQuery("subject_name", req.getKeyword()).boost(2.0f))
                .should(QueryBuilders.matchQuery("subject_answer", req.getKeyword()).boost(1.0f))
                .minimumShouldMatch(1);

        HighlightBuilder highlightBuilder = new HighlightBuilder()
                .field("*")
                .requireFieldMatch(false)
                .preTags("<span style=\"color:red\">")
                .postTags("</span>");

        EsSearchRequest esSearchRequest = EsSearchRequest.builder()
                .bq(bq)
                .fields(new String[]{"subject_id", "subject_name", "subject_answer", "subject_type", "doc_id", "create_user", "create_time"})
                .from((req.getPageNum() - 1) * req.getPageSize())
                .size(req.getPageSize())
                .needScroll(false)
                .minutes(null)
                .sortName("_score")
                .sortOrder(SortOrder.DESC)
                .highlightBuilder(highlightBuilder)
                .build();

        log.info("Generated EsSearchRequest: {}", JSONUtil.toJsonStr(esSearchRequest));
        return esSearchRequest;
    }

    private SubjectInfoEs convertResult(SearchHit hit) {
        Map<String, Object> sourceAsMap = hit.getSourceAsMap();
        if (CollUtil.isEmpty(sourceAsMap)) {
            return null;
        }
        SubjectInfoEs result = new SubjectInfoEs();
        result.setSubjectId(MapUtils.getLong(sourceAsMap, EsSubjectFields.SUBJECT_ID));
        result.setSubjectName(MapUtils.getString(sourceAsMap, EsSubjectFields.SUBJECT_NAME));

        result.setSubjectAnswer(MapUtils.getString(sourceAsMap, EsSubjectFields.SUBJECT_ANSWER));

        result.setDocId(MapUtils.getLong(sourceAsMap, EsSubjectFields.DOC_ID));
        result.setSubjectType(MapUtils.getInteger(sourceAsMap, EsSubjectFields.SUBJECT_TYPE));
        result.setScore(new BigDecimal(String.valueOf(hit.getScore())).multiply(new BigDecimal("100.00")
                .setScale(2, RoundingMode.HALF_UP)));

        //处理name的高亮
        Map<String, HighlightField> highlightFields = hit.getHighlightFields();
        HighlightField subjectNameField = highlightFields.get(EsSubjectFields.SUBJECT_NAME);
        if (Objects.nonNull(subjectNameField)) {
            Text[] fragments = subjectNameField.getFragments();
            StringBuilder subjectNameBuilder = new StringBuilder();
            for (Text fragment : fragments) {
                subjectNameBuilder.append(fragment);
            }
            result.setSubjectName(subjectNameBuilder.toString());
        }

        //处理答案高亮
        HighlightField subjectAnswerField = highlightFields.get(EsSubjectFields.SUBJECT_ANSWER);
        if (Objects.nonNull(subjectAnswerField)) {
            Text[] fragments = subjectAnswerField.getFragments();
            StringBuilder subjectAnswerBuilder = new StringBuilder();
            for (Text fragment : fragments) {
                subjectAnswerBuilder.append(fragment);
            }
            result.setSubjectAnswer(subjectAnswerBuilder.toString());
        }
        return result;
    }
}
